home *** CD-ROM | disk | FTP | other *** search
/ Aminet 37 / Aminet 37 (2000)(Schatztruhe)[!][Jun 2000].iso / Aminet / misc / emu / A2DiskDump.lha / A2DiskDump / A2DiskDump.c next >
Encoding:
C/C++ Source or Header  |  2000-02-27  |  28.9 KB  |  996 lines

  1. /******************************************************************************
  2.  * A2DiskDump - A utility for extracting all files from an Apple II DOS 3.3
  3.  * disk image and dumping them into individual files.
  4.  *
  5.  * $Header: Big:Archives/Apple II/A2DiskDump/RCS/A2DiskDump.c,v 1.9 2000/02/27 16:38:11 AGMS Exp $
  6.  *
  7.  * Implemented by Alexander G. M. Smith, Ottawa Canada, agmsmith@achilles.net,
  8.  * agmsmith@bix.com, 71330.3173@compuserve.com, and various other places.
  9.  *
  10.  * This code is put into the public domain by AGMS, so you can copy it,
  11.  * hack it up, sell it, and do whatever you want to it.
  12.  *
  13.  * Compile with the SAS C compiler version 6.58, using 32 bit integers.
  14.  *
  15.  * Tested under AmigaDOS 2.1, 68030.  Should work on 68000 systems too.
  16.  *
  17.  * $Log: A2DiskDump.c,v $
  18.  * Revision 1.9  2000/02/27  16:38:11  AGMS
  19.  * Mention random access sparse files.
  20.  *
  21.  * Revision 1.8  2000/02/27  13:08:41  AGMS
  22.  * Made quiet mode the default.  Also better printing of deleted files.
  23.  *
  24.  * Revision 1.7  2000/02/17  17:18:22  AGMS
  25.  * Added option to convert text files to readable form, and to
  26.  * only process text files.
  27.  *
  28.  * Revision 1.6  2000/02/17  16:21:26  AGMS
  29.  * Made program name a bit shorter.
  30.  *
  31.  * Revision 1.5  2000/02/14  10:44:44  AGMS
  32.  * First working version.
  33.  *
  34.  * Revision 1.4  2000/02/12  16:56:23  AGMS
  35.  * Under construction.
  36.  *
  37.  * Revision 1.3  2000/02/07  17:39:08  AGMS
  38.  * Under construction.
  39.  *
  40.  * Revision 1.2  2000/02/07  16:03:02  AGMS
  41.  * Command line parsing is done, now working on reading the catalog.
  42.  *
  43.  * Revision 1.1  2000/02/07  12:33:56  AGMS
  44.  * Initial revision
  45.  */
  46.  
  47. #ifdef __SASC
  48.   #define __USE_SYSBASE 1
  49.     /* Need this to make the exec.library headers use the library base global
  50.     variable SysBase in SAS C (otherwise it just uses location $4). */
  51. #endif
  52.  
  53. #include <proto/utility.h>
  54. #include <proto/dos.h>
  55.  
  56. #include <ctype.h>
  57. #include <stdlib.h>
  58. #include <stdio.h>
  59. #include <string.h>
  60.  
  61. #ifndef WORKBENCH_STARTUP_H
  62.   #include <workbench/startup.h>
  63. #endif
  64.  
  65.  
  66.  
  67. /******************************************************************************
  68.  * Command line argument parsing stuff.
  69.  */
  70.  
  71. static const char RDArgsTemplate [] =
  72.   "DISKIMAGE/A,DUMPFILES/S,NAMEPREFIX,DROPHEADER/S,KEEPTAIL/S,CONVERTTEXT/S,TEXTFILESONLY/S,NOCOMMENT/S,VERBOSE/S";
  73.   /* Template for parsing the command line, native Amiga style. */
  74.  
  75. enum RDArgsEnum {
  76.   DISKIMAGE_RDARGINDEX = 0,
  77.   DUMPFILES_RDARGINDEX,
  78.   NAMEPREFIX_RDARGINDEX,
  79.   DROPHEADER_RDARGINDEX,
  80.   KEEPTAIL_RDARGINDEX,
  81.   CONVERTTEXT_RDARGINDEX,
  82.   TEXTFILESONLY_RDARGINDEX,
  83.   NOCOMMENT_RDARGINDEX,
  84.   VERBOSE_RDARGINDEX,
  85.   NUMRDARGS
  86. };
  87.   /* Argument numbers corresponding to the command line parsing template. */
  88.  
  89.  
  90. char InputName [256];
  91.   /* Name of the file to use for the disk image. */
  92.  
  93. BOOL DumpFiles;
  94.   /* TRUE to dump out the files, FALSE if you just want a catalog listing. */
  95.  
  96. char NamePrefix [256];
  97.   /* This string is pre-pended to the file names being dumped.  Usually
  98.   it will be a directory name, but it can be an arbitrary prefix too. */
  99.  
  100. BOOL DropHeader;
  101.   /* TRUE if the user doesn't want the extra 4 bytes of start address and
  102.   length in Apple binary files.  FALSE (the default) leaves the bytes in.
  103.   Only affects binary files, not BASIC etc. */
  104.  
  105. BOOL KeepTail;
  106.   /* TRUE if the user wants to keep the extra garbage bytes past the normal
  107.   end of the file which fill the last disk sector in the file.  The default
  108.   of FALSE doesn't write the extra data (stops at the first NUL in text
  109.   files and stops after the length in binary files and BASIC files). */
  110.  
  111. BOOL ConvertText;
  112.   /* If TRUE then text files will have the high bit stripped and the
  113.   carriage return converted to a line feed. */
  114.  
  115. BOOL TextFilesOnly;
  116.   /* If TRUE then the dump will only dump text files, skipping over
  117.   binary and all other file types. */
  118.  
  119. BOOL NoComment;
  120.   /* TRUE to not write the file type, size, length etc. into the file's comment
  121.   field, FALSE for the default of having a comment. */
  122.  
  123. BOOL Verbose;
  124.   /* TRUE to turn on extra progress messages, FALSE in quiet mode. */
  125.  
  126.  
  127. const char HelpText [] =
  128.   "A2DiskDump is a utility for extracting all files from an Apple II\n"
  129.   "DOS 3.3 disk image file (like the kinds made from ADT the Apple Disk\n"
  130.   "Transfer utility).  The files are written to the current directory,\n"
  131.   "using the name from the disk's catalog, with illegal characters\n"
  132.   "replaced.  By default, a comment is attached to each file with its\n"
  133.   "catalog listing plus secret info (file length, start address).\n"
  134.   "\n"
  135.   "Public domain by Alexander G. M. Smith, Ottawa Canada, send questions to\n"
  136.   "agmsmith@achilles.net, agmsmith@bix.com, 71330.3173@compuserve.com.\n"
  137.   "$VER: A2DiskDump 1.0 " __AMIGADATE__ " ($Id: A2DiskDump.c,v 1.9 2000/02/27 16:38:11 AGMS Exp $)\n"
  138.   "\n"
  139.   "DISKIMAGE/A is the required path name to the disk image file, which\n"
  140.   "is from an Apple II DOS 3.3 disk (256 bytes per sector, 16 sectors per\n"
  141.   "track, 35 tracks) and is 143360 bytes long.\n"
  142.   "\n"
  143.   "DROPHEADER/S is a switch which if present, leaves out the header info\n"
  144.   "in binary files.  Otherwise, binary files will have a 4 byte header\n"
  145.   "containing address low/high bytes followed by length low/high.  Text\n"
  146.   "and other file types aren't affected by this switch.\n"
  147.   "\n"
  148.   "KEEPTAIL/S is a switch which when present, includes the garbage past\n"
  149.   "the end of the file (end of file determined by the header for binary\n"
  150.   "files and BASIC files, or first NUL in text files).  This is useful\n"
  151.   "for random access text files used in databases, where the file contains\n"
  152.   "NUL characters for fixed length record padding.  Yes, the code does\n"
  153.   "handle sparse files, filling in the non-existent sectors with zeroes.\n"
  154.   "\n"
  155.   "CONVERTTEXT/S when present switches on conversion of text files from\n"
  156.   "Apple II format into text file format (high bit set to zero and\n"
  157.   "carriage return characters turned into line feeds).\n"
  158.   "\n"
  159.   "TEXTFILESONLY/S makes the dumpfile feature only dump text files,\n"
  160.   "skipping over all other file types.\n"
  161.   "\n"
  162.   "NOCOMMENT/S specify this switch if you don't want to have comments\n"
  163.   "attached to the files.  The default is to have comments, with the\n"
  164.   "comment text being much like an Apple II catalog listing.\n"
  165.   "\n"
  166.   "VERBOSE/S switches on debug messages and extra status info etc.\n"
  167.   "\n";
  168.  
  169.  
  170.  
  171. /******************************************************************************
  172.  * Structures defining the layout of the data on an Apple DOS 3.3 disk.
  173.  */
  174.  
  175. typedef struct TSPairStruct
  176. {
  177.   unsigned char track;
  178.   unsigned char sector;
  179. } TSPair;
  180.  
  181.  
  182. typedef struct VTOCStruct /* Volume table of contents at track $11, sector 0 */
  183. {
  184.   char filler1 [1];
  185.   TSPair firstDirectory;
  186.   unsigned char dosVersion;
  187.   char filler2 [2];
  188.   unsigned char volumeNumber;
  189.   char filler3 [45];
  190.   unsigned char tracksPerDisk;
  191.   unsigned char sectorsPerTrack;
  192.   unsigned char bytesPerSectorLowByte;
  193.   unsigned char bytesPerSectorHighByte;
  194.   unsigned long sectorsAllocatedInTrackMask [35];
  195. } VTOC;
  196.  
  197.  
  198. typedef struct FileEntryStruct
  199. {
  200.   TSPair firstTrackSectorList;
  201.   unsigned char fileType;
  202.   char fileName [30];
  203.   unsigned char numberOfSectorsUsedLowByte;
  204.   unsigned char numberOfSectorsUsedHighByte;
  205. } FileEntry;
  206.  
  207.  
  208. typedef struct DirectoryStruct /* Directory sector */
  209. {
  210.   char filler1 [1];
  211.   TSPair nextDirectory;
  212.   char filler2 [8];
  213.   FileEntry fileEntry[7];
  214. } Directory;
  215.  
  216.  
  217. typedef struct TrackSectorListStruct
  218. {
  219.   char filler1 [1];
  220.   TSPair nextTSList;
  221.   char filler2 [2];
  222.   unsigned char fileOffsetLow;
  223.   unsigned char fileOffsetHigh;
  224.   char filler3 [5];
  225.   TSPair list[122];
  226. } TrackSectorList;
  227.  
  228.  
  229. typedef struct BinaryFileHeaderStruct /* Start of a binary file */
  230. {
  231.   unsigned char addressLow;
  232.   unsigned char addressHigh;
  233.   unsigned char lengthLow;
  234.   unsigned char lengthHigh;
  235. } BinaryFileHeader;
  236.  
  237.  
  238. typedef struct BasicFileHeaderStruct /* Start of a BASIC (INT and FP) file */
  239. {
  240.   unsigned char lengthLow;
  241.   unsigned char lengthHigh;
  242. } BasicFileHeader;
  243.  
  244.  
  245.  
  246. /******************************************************************************
  247.  * Things too large to be on the stack or that are global.
  248.  */
  249.  
  250. char ErrorMessage [256];
  251.   /* For formatting error messages into. */
  252.  
  253. FILE *InputFile;
  254.   /* The disk image input file.  NULL if closed. */
  255.  
  256. FILE *OutputFile;
  257.   /* The currently open output file.  NULL if closed. */
  258.  
  259. __far char DiskImage [35 * 16 * 256];
  260.   /* The whole disk image file gets read into this array for processing.
  261.   It is declared as __far since it is more than 32K and thus won't fit
  262.   in the near data section and thus uses full 32 bit addresses rather
  263.   than register A4 relative with a 16 bit offset. */
  264.  
  265. int VolumeNumber;
  266.   /* The volume number of the disk. */
  267.  
  268. #define MAX_COMPLETE_TSLIST_SIZE 65536
  269.  
  270. __far TSPair CompleteSectorList [MAX_COMPLETE_TSLIST_SIZE];
  271.   /* A list of all the track/sectors in a particular file.  This array
  272.   is actually bigger than all the memory in an Apple II! */
  273.  
  274. int NumberOfSectorsInCompleteSectorList;
  275.   /* Number of elements in the CompleteSectorList array. */
  276.  
  277. __far char ZeroesBuffer [256];
  278.   /* A buffer full of zeroes for writing into unallocated sectors. */
  279.  
  280.  
  281.  
  282. /******************************************************************************
  283.  * Look at the argument values the user specified and try to make sense of
  284.  * them.  Also generates defaults for unspecified settings.
  285.  */
  286.  
  287. BOOL ExamineArguments (LONG ArgumentValues [NUMRDARGS])
  288. {
  289.   /* Get the disk image path name. */
  290.  
  291.   strncpy (InputName,
  292.     (char *) ArgumentValues [DISKIMAGE_RDARGINDEX],
  293.     sizeof (InputName));
  294.  
  295.   if (InputName[0] == 0)
  296.   {
  297.     printf ("Need a longer disk image file name.\n");
  298.     return FALSE;
  299.   }
  300.  
  301.   if (ArgumentValues [NAMEPREFIX_RDARGINDEX] != 0)
  302.     strncpy (NamePrefix,
  303.     (char *) ArgumentValues [NAMEPREFIX_RDARGINDEX],
  304.     sizeof (NamePrefix));
  305.   else
  306.     NamePrefix[0] = 0;
  307.  
  308.   /* Read the various switch arguments. */
  309.  
  310.   DumpFiles = (ArgumentValues [DUMPFILES_RDARGINDEX] != 0);
  311.   DropHeader = (ArgumentValues [DROPHEADER_RDARGINDEX] != 0);
  312.   KeepTail = (ArgumentValues [KEEPTAIL_RDARGINDEX] != 0);
  313.   ConvertText = (ArgumentValues [CONVERTTEXT_RDARGINDEX] != 0);
  314.   TextFilesOnly = (ArgumentValues [TEXTFILESONLY_RDARGINDEX] != 0);
  315.   NoComment = (ArgumentValues [NOCOMMENT_RDARGINDEX] != 0);
  316.   Verbose = (ArgumentValues [VERBOSE_RDARGINDEX] != 0);
  317.  
  318.   if (Verbose)
  319.     printf (
  320.     "A2DiskDump compiled on " __DATE__ " at " __TIME__  ".\n"
  321.     "Disk image input file: \"%s\"\n"
  322.     "Dump Files: %s\n"
  323.     "Output file name prefix: \"%s\"\n"
  324.     "Drop header: %s\n"
  325.     "Keep tail: %s\n"
  326.     "Convert text: %s\n"
  327.     "Text files only: %s\n"
  328.     "Attach comments: %s\n"
  329.     "Verbose mode is obviously turned on.\n",
  330.     InputName,
  331.     DumpFiles ? "yes (copy each file in image to a real file)" :
  332.     "no (just do a catalog listing)",
  333.     NamePrefix,
  334.     DropHeader ? "yes (remove header bytes from binary and BASIC files)" :
  335.     "no (copy data as-is from start of file in disk image)",
  336.     KeepTail ? "yes (keep garbage past end of file marker)" :
  337.     "no (remove garbage past end of file)",
  338.     ConvertText ? "yes (CR to LF and strip high bit in text files)" : "no",
  339.     TextFilesOnly ? "yes (dump only does text files)" :
  340.     "no (dump does all files)",
  341.     NoComment ? "no (attach comments to dumped files)" : "yes");
  342.  
  343.   return TRUE;
  344. }
  345.  
  346.  
  347.  
  348. /******************************************************************************
  349.  * Read the command line arguments and set up various things.  Returns TRUE
  350.  * if successful.  FALSE if not started from the command line etc.
  351.  */
  352.  
  353. BOOL ParseArguments (void)
  354. {
  355.   LONG            ArgumentValues [NUMRDARGS];
  356.   struct TagItem  DoneTag = {TAG_DONE, 0};
  357.   LONG            ErrorCode;
  358.   struct RDArgs  *RDArgParametersPntr;
  359.   BOOL            ReturnCode;
  360.  
  361.   if (_WBenchMsg != NULL)
  362.     return FALSE;  /* If started from the workbench, no command line exists. */
  363.  
  364.   RDArgParametersPntr = AllocDosObject (DOS_RDARGS, &DoneTag);
  365.   if (RDArgParametersPntr == NULL)
  366.   {
  367.     printf ("Out of memory for allocating RDARGS structure.\n");
  368.     return FALSE;
  369.   }
  370.  
  371.   RDArgParametersPntr->RDA_ExtHelp = (char *) HelpText;
  372.  
  373.   memset (ArgumentValues, 0, sizeof (ArgumentValues));
  374.  
  375.   ReturnCode = (NULL != ReadArgs ((STRPTR) RDArgsTemplate,
  376.   ArgumentValues, RDArgParametersPntr));
  377.  
  378.   if (ReturnCode)
  379.     ReturnCode = ExamineArguments (ArgumentValues);
  380.   else
  381.   {
  382.     ErrorCode = IoErr ();
  383.     strcpy (ErrorMessage, ": ?");
  384.     Fault (ErrorCode, (char *) "", ErrorMessage, sizeof (ErrorMessage));
  385.     printf ("Oops%s, please use \"A2DiskDump ?\" for\n"
  386.     "help and type a second \"?\" to get even more help.\n", ErrorMessage);
  387.   }
  388.  
  389.   FreeArgs (RDArgParametersPntr);
  390.   FreeDosObject (DOS_RDARGS, RDArgParametersPntr);
  391.  
  392.   return ReturnCode;
  393. }
  394.  
  395.  
  396.  
  397. /******************************************************************************
  398.  * Utility function for getting a pointer into the DiskImage array starting
  399.  * at the given track and sector.  If the track or sector is invalid, it
  400.  * will return NULL.
  401.  */
  402.  
  403. void * DiskImageAtSector (unsigned char Track, unsigned char Sector)
  404. {
  405.   if (Track >= 35)
  406.   {
  407.     printf ("Bad track requested for track $%02X, sector $%X.\n",
  408.       (int) Track, (int) Sector);
  409.     return NULL;
  410.   }
  411.  
  412.   if (Sector >= 16)
  413.   {
  414.     printf ("Bad sector requested for track $%02X, sector $%X.\n",
  415.       (int) Track, (int) Sector);
  416.     return NULL;
  417.   }
  418.  
  419.   /* Track and sector both zero means end of list of sectors etc,
  420.      normally it won't appear in actual use. */
  421.  
  422.   if (Track == 0 && Sector == 0)
  423.     return NULL;
  424.  
  425.   return DiskImage + (Track * 16L * 256L + Sector * 256L);
  426. }
  427.  
  428.  
  429.  
  430. /******************************************************************************
  431.  * Given a pointer to the first track/sector list's sector, builds a list
  432.  * of all the sectors in the file, stored in the global CompleteSectorList.
  433.  * Returns TRUE if successful, FALSE if something went wrong.
  434.  */
  435.  
  436. BOOL BuildSectorList (TSPair FirstTrackSectorList)
  437. {
  438.   TSPair            CurrentTSListTS;
  439.   int               i, j;
  440.   TrackSectorList  *TSListPntr;
  441.   int               WriteIndex;
  442.  
  443.   CurrentTSListTS = FirstTrackSectorList;
  444.   NumberOfSectorsInCompleteSectorList = 0;
  445.   memset (CompleteSectorList, 0, sizeof (CompleteSectorList));
  446.  
  447.   while (CurrentTSListTS.track != 0 || CurrentTSListTS.sector != 0)
  448.   {
  449.     if (Verbose)
  450.       printf ("Processing track/sector list at $%02X/%X:\n",
  451.       (int) CurrentTSListTS.track, (int) CurrentTSListTS.sector);
  452.  
  453.     TSListPntr = DiskImageAtSector (CurrentTSListTS.track,
  454.       CurrentTSListTS.sector);
  455.     if (TSListPntr == NULL)
  456.     {
  457.       printf ("Track/sector list in sector $%02X/%X doesn't exist, corrupt?\n",
  458.         (int) CurrentTSListTS.track, (int) CurrentTSListTS.sector);
  459.       return FALSE;
  460.     }
  461.  
  462.     WriteIndex = TSListPntr->fileOffsetLow + 256L * TSListPntr->fileOffsetHigh;
  463.  
  464.     if (Verbose)
  465.     {
  466.       for (i = 0, j = 0; i < 122; i++)
  467.         if (TSListPntr->list[i].track != 0 ||
  468.         TSListPntr->list[i].sector != 0)
  469.           j++;
  470.       printf ("This TSlist has a sector offset of %u and has %d non-zero sectors.\n",
  471.       (int) WriteIndex, j);
  472.     }
  473.  
  474.     for (i = 0; i < 122; i++)
  475.     {
  476.       if (WriteIndex >= MAX_COMPLETE_TSLIST_SIZE)
  477.       {
  478.         printf ("Too many sectors (got %d so far) in the combined "
  479.           "track/sector lists.\n", (int) WriteIndex);
  480.         return FALSE;
  481.       }
  482.       CompleteSectorList [WriteIndex++] = TSListPntr->list[i];
  483.     }
  484.  
  485.     if (WriteIndex > NumberOfSectorsInCompleteSectorList)
  486.       NumberOfSectorsInCompleteSectorList = WriteIndex;
  487.  
  488.     /* Get the next TS list, use individual field transfers since the
  489.     structure isn't at an even address so 68000 CPUs will crash if
  490.     transfering it in one 16 bit quantity. */
  491.  
  492.     CurrentTSListTS.track = TSListPntr->nextTSList.track;
  493.     CurrentTSListTS.sector = TSListPntr->nextTSList.sector;
  494.   }
  495.  
  496.   /* Trim off the empty sectors at the end of the list. */
  497.  
  498.   i = NumberOfSectorsInCompleteSectorList;
  499.   while (i > 0)
  500.   {
  501.     i--;
  502.     if (CompleteSectorList[i].sector != 0 || CompleteSectorList[i].track != 0)
  503.     {
  504.       i++;
  505.       break;
  506.     }
  507.   }
  508.  
  509.   if (Verbose)
  510.     printf ("Track/sector list reduced from %d sector entries to %d.\n",
  511.     (int) NumberOfSectorsInCompleteSectorList, (int) i);
  512.  
  513.   NumberOfSectorsInCompleteSectorList = i;
  514.  
  515.   if (Verbose)
  516.   {
  517.     printf ("Sectors used by file:");
  518.     for (i = 0; i < NumberOfSectorsInCompleteSectorList; i++)
  519.       printf (" $%02X/%X", (int) CompleteSectorList[i].track,
  520.       (int) CompleteSectorList[i].sector);
  521.     printf ("\n");
  522.   }
  523.  
  524.   return TRUE;
  525. }
  526.  
  527.  
  528.  
  529. /******************************************************************************
  530.  * Dump a single file from the image.  Returns TRUE if successful.
  531.  */
  532.  
  533. BOOL DumpSingleFile (FileEntry *FileEntryPntr)
  534. {
  535.   int               ActualFileLength;
  536.   int               AmountToWrite;
  537.   BasicFileHeader  *BasicFileHeaderPntr;
  538.   BinaryFileHeader *BinaryFileHeaderPntr;
  539.   char             *BufferToWrite;
  540.   char              Comment [80];
  541.   TSPair            TSListTS;
  542.   char              FixedFileName [40];
  543.   char              Letter;
  544.   int               i, j;
  545.   char              OutputFileName [300];
  546.   char              ReadableFileName [40];
  547.   int               SizeToWrite;
  548.   BOOL              SkipFirstFourBytes;
  549.   char              TextBuffer [256];
  550.   char             *TextPntr;
  551.  
  552.   /* Watch out for odd aligned file info. */
  553.  
  554.   TSListTS.track = FileEntryPntr->firstTrackSectorList.track;
  555.   TSListTS.sector = FileEntryPntr->firstTrackSectorList.sector;
  556.  
  557.   /* Copy the file name, stripping the Apple's high bit and
  558.      changing control characters to readable characters. */
  559.  
  560.   for (i = 0; i < 30; i++)
  561.   {
  562.     Letter = FileEntryPntr->fileName[i];
  563.     Letter = (Letter & 0x7F);
  564.     if (Letter < 32)
  565.       Letter = '_'; /* Replace control chars with underscore. */
  566.     ReadableFileName[i] = Letter;
  567.     if (Letter == ':' || Letter == '/' || Letter == '\\')
  568.       Letter = '_'; /* Remove illegal characters for file names. */
  569.     FixedFileName[i] = Letter;
  570.   }
  571.   ReadableFileName[30] = 0;
  572.  
  573.   /* Remove trailing blanks and other space things. */
  574.  
  575.   for (i = 29; i >= 0; i--)
  576.   {
  577.     if (!isspace (FixedFileName[i]))
  578.       break;
  579.   }
  580.   FixedFileName[i+1] = 0;
  581.  
  582.   /* Deleted files use track $FF, last directory entry marked by $00/0. */
  583.  
  584.   if (TSListTS.track != 0xFF &&
  585.   (TSListTS.track != 0 || TSListTS.sector != 0))
  586.   {
  587.     Comment[0] = (FileEntryPntr->fileType & 0x80) ? '*' : ' '; /* Locked */
  588.     Comment[1] = 0;
  589.     if ((FileEntryPntr->fileType & 0x7F) == 0)
  590.       strcat (Comment, "T"); /* Text file */
  591.     if (FileEntryPntr->fileType & 0x40)
  592.       strcat (Comment, "L"); /* Alternate Type B - usually used for Lisa assembler source. */
  593.     if (FileEntryPntr->fileType & 0x20)
  594.       strcat (Comment, "a"); /* Type A - not the usual kind though. */
  595.     if (FileEntryPntr->fileType & 0x10)
  596.       strcat (Comment, "s"); /* Type S - another unusual kind. */
  597.     if (FileEntryPntr->fileType & 0x08)
  598.       strcat (Comment, "R"); /* Relocatable */
  599.     if (FileEntryPntr->fileType & 0x04)
  600.       strcat (Comment, "B"); /* Binary */
  601.     if (FileEntryPntr->fileType & 0x02)
  602.       strcat (Comment, "A"); /* Applesoft BASIC */
  603.     if (FileEntryPntr->fileType & 0x01)
  604.       strcat (Comment, "I"); /* Integer BASIC */
  605.  
  606.     sprintf (Comment + strlen (Comment), " %03d %s",
  607.       (int) (FileEntryPntr->numberOfSectorsUsedLowByte +
  608.       256 *FileEntryPntr->numberOfSectorsUsedHighByte),
  609.       ReadableFileName);
  610.  
  611.     if (Verbose)
  612.       printf ("\nFixed file name is \"%s\".\n", FixedFileName);
  613.  
  614.     /* Make a list of all the sectors used by the file. */
  615.  
  616.     if (!BuildSectorList (TSListTS))
  617.       return FALSE;
  618.  
  619.     /* Extract the header info for binary and BASIC files, and find the
  620.        exact size of the file (BASIC and Binary length from header plus
  621.        header size, position of first NUL for Text files). */
  622.  
  623.     ActualFileLength = NumberOfSectorsInCompleteSectorList * 256;
  624.  
  625.     if (FileEntryPntr->fileType & 0x04) /* Binary files. */
  626.     {
  627.       BinaryFileHeaderPntr = DiskImageAtSector (CompleteSectorList[0].track,
  628.         CompleteSectorList[0].sector);
  629.       if (BinaryFileHeaderPntr == NULL)
  630.         return FALSE;
  631.       sprintf (Comment + strlen (Comment), ",A$%X,L$%X",
  632.         (int) (BinaryFileHeaderPntr->addressLow +
  633.         256 * BinaryFileHeaderPntr->addressHigh),
  634.         (int) (BinaryFileHeaderPntr->lengthLow +
  635.         256 * BinaryFileHeaderPntr->lengthHigh));
  636.       ActualFileLength = BinaryFileHeaderPntr->lengthLow +
  637.         256 * BinaryFileHeaderPntr->lengthHigh +
  638.         4 /* For header size */;
  639.     }
  640.  
  641.     if (FileEntryPntr->fileType & 0x03) /* All BASIC types of files. */
  642.     {
  643.       BasicFileHeaderPntr = DiskImageAtSector (CompleteSectorList[0].track,
  644.         CompleteSectorList[0].sector);
  645.       if (BasicFileHeaderPntr == NULL)
  646.         return FALSE;
  647.       sprintf (Comment + strlen (Comment), ",L$%X",
  648.         (int) (BasicFileHeaderPntr->lengthLow +
  649.         256 * BasicFileHeaderPntr->lengthHigh));
  650.       ActualFileLength = BasicFileHeaderPntr->lengthLow +
  651.         256 * BasicFileHeaderPntr->lengthHigh +
  652.         2 /* For header size */;
  653.     }
  654.  
  655.     if ((FileEntryPntr->fileType & 0x7F) == 0) /* Text files. */
  656.     {
  657.       /* Find the end of the file - first NUL in the file. */
  658.  
  659.       for (i = 0; i < NumberOfSectorsInCompleteSectorList; i++)
  660.       {
  661.         if (CompleteSectorList[i].track == 0 &&
  662.         CompleteSectorList[i].sector == 0)
  663.         {
  664.           /* Found an unallocated sector, equivalent to all zeroes. */
  665.  
  666.           ActualFileLength = 256 * i;
  667.           break;
  668.         }
  669.  
  670.         /* Look inside a data sector for the NUL. */
  671.  
  672.         TextPntr = DiskImageAtSector (CompleteSectorList[i].track,
  673.           CompleteSectorList[i].sector);
  674.         if (TextPntr == NULL)
  675.           return FALSE;
  676.         for (j = 0; j < 256; j++)
  677.         {
  678.           if (TextPntr[j] == 0)
  679.             break;
  680.         }
  681.         if (j < 256)
  682.         {
  683.           ActualFileLength = 256 * i + j;
  684.           break;
  685.         }
  686.       }
  687.     }
  688.  
  689.     if (KeepTail)
  690.       SizeToWrite = NumberOfSectorsInCompleteSectorList * 256;
  691.     else
  692.       SizeToWrite = ActualFileLength;
  693.  
  694.     if (DropHeader && (FileEntryPntr->fileType & 0x04))
  695.     {
  696.       SizeToWrite -= 4; /* Skip 4 byte header when dumping binary files. */
  697.       SkipFirstFourBytes = TRUE;
  698.     }
  699.     else
  700.       SkipFirstFourBytes = FALSE;
  701.  
  702.     /* Try to open the output file. */
  703.  
  704.     strcpy (OutputFileName, NamePrefix);
  705.     strcat (OutputFileName, FixedFileName);
  706.  
  707.     if (DumpFiles &&
  708.     ((FileEntryPntr->fileType & 0x7F) == 0 || !TextFilesOnly) &&
  709.     strlen (FixedFileName) > 0 && SizeToWrite > 0)
  710.     {
  711.       if (Verbose)
  712.         printf ("Opening output file \"%s\".\n", OutputFileName);
  713.  
  714.       OutputFile = fopen (OutputFileName, "wb");
  715.  
  716.       if (OutputFile == NULL)
  717.         printf ("Unable to open file \"%s\".\n", OutputFileName);
  718.     }
  719.     else
  720.     {
  721.       if (Verbose)
  722.         printf ("Not writing \"%s\".\n", OutputFileName);
  723.     }
  724.  
  725.     if (OutputFile != NULL)
  726.     {
  727.       for (i = 0; SizeToWrite > 0; i++)
  728.       {
  729.         if (i >= NumberOfSectorsInCompleteSectorList ||
  730.         (CompleteSectorList[i].track == 0 &&
  731.         CompleteSectorList[i].sector == 0))
  732.         {
  733.           /* Found an unallocated sector, write all zeroes. */
  734.  
  735.           BufferToWrite = ZeroesBuffer;
  736.         }
  737.         else
  738.         {
  739.           BufferToWrite = DiskImageAtSector (CompleteSectorList[i].track,
  740.             CompleteSectorList[i].sector);
  741.           if (BufferToWrite == NULL)
  742.             return FALSE;
  743.         }
  744.  
  745.         AmountToWrite = 256;
  746.  
  747.         if (SkipFirstFourBytes)
  748.         {
  749.           SkipFirstFourBytes = FALSE;
  750.           BufferToWrite += 4;
  751.           AmountToWrite -= 4;
  752.         }
  753.  
  754.         if (AmountToWrite > SizeToWrite)
  755.           AmountToWrite = SizeToWrite;
  756.  
  757.         if (ConvertText && (FileEntryPntr->fileType & 0x7F) == 0)
  758.         {
  759.           /* Strip the high bit and convert CR to LF. */
  760.  
  761.           for (j = 0; j < AmountToWrite; j++)
  762.           {
  763.             Letter = (0x7F & BufferToWrite[j]);
  764.             if (Letter == '\r') /* Convert carriage return to line feed. */
  765.               Letter = '\n';
  766.             TextBuffer[j] = Letter;
  767.           }
  768.           BufferToWrite = TextBuffer;
  769.         }
  770.  
  771.         if (fwrite (BufferToWrite, 1, AmountToWrite, OutputFile) !=
  772.         AmountToWrite)
  773.         {
  774.           printf ("Error while writing data to \"%s\".\n", OutputFileName);
  775.           return FALSE;
  776.         }
  777.  
  778.         SizeToWrite -= AmountToWrite;
  779.       }
  780.  
  781.       fclose (OutputFile);
  782.       OutputFile = NULL;
  783.  
  784.       /* Attach the comment text to the file. */
  785.  
  786.       if (!NoComment)
  787.         SetComment (OutputFileName, Comment);
  788.     }
  789.  
  790.     /* Print the Apple II style catalog listing line. */
  791.  
  792.     printf ("%s\n", Comment);
  793.   }
  794.   else
  795.   {
  796.     if (Verbose)
  797.     {
  798.       if (TSListTS.track == 0xFF)
  799.         printf ("Ignoring deleted file: \"%s\"\n", ReadableFileName);
  800.       else if (TSListTS.track != 0)
  801.         printf ("A weird file with TSList on track $%02X: \"%s\"\n",
  802.         (int) TSListTS.track, ReadableFileName);
  803.       /* else track 0 - an empty file entry, don't print. */
  804.     }
  805.   }
  806.  
  807.   return TRUE;
  808. }
  809.  
  810.  
  811.  
  812. /******************************************************************************
  813.  * This function opens the disk image file and spins through the catalog
  814.  * entries in it, calling a file extractor subroutine for each file inside it.
  815.  * Returns TRUE if successful, FALSE if something went wrong.
  816.  */
  817.  
  818. BOOL BreakUpDiskImage (void)
  819. {
  820.   unsigned short  BytesPerSector;
  821.   BOOL            Corrupt;
  822.   Directory      *DirectoryPntr;
  823.   TSPair          DirectoryTS;
  824.   FileEntry      *FileEntryPntr;
  825.   int             FileNumber;
  826.   int             ImageFileSize;
  827.   VTOC           *VTOCPntr;
  828.  
  829.   if (Verbose)
  830.     printf ("Opening disk image file...\n");
  831.  
  832.   InputFile = fopen (InputName, "rb");
  833.   if (InputFile == NULL)
  834.   {
  835.     printf ("Unable to open \"%s\".\n", InputName);
  836.     return FALSE;
  837.   }
  838.  
  839.   if (fseek (InputFile, 0, SEEK_END) == -1)
  840.   {
  841.     printf ("Unable to seek to end of file \"%s\".\n", InputName);
  842.     return FALSE;
  843.   }
  844.  
  845.   ImageFileSize = ftell (InputFile);
  846.   if (ImageFileSize == -1)
  847.   {
  848.     printf ("Unable to find size of file \"%s\".\n", InputName);
  849.     return FALSE;
  850.   }
  851.  
  852.   if (fseek (InputFile, 0, SEEK_SET) == -1)
  853.   {
  854.     printf ("Unable to seek to beginning of file \"%s\".\n", InputName);
  855.     return FALSE;
  856.   }
  857.  
  858.   if (ImageFileSize != sizeof (DiskImage))
  859.   {
  860.     printf ("File \"%s\" isn't a disk image, its size is %d, not %d.\n",
  861.       InputName, ImageFileSize, sizeof (DiskImage));
  862.     return FALSE;
  863.   }
  864.  
  865.   if (Verbose)
  866.     printf ("Reading disk image file into memory...\n");
  867.  
  868.   if (fread (DiskImage, sizeof (DiskImage), 1, InputFile) != 1)
  869.   {
  870.     printf ("Unable to read the whole disk image into memory from \"%s\".\n",
  871.       InputName);
  872.     return FALSE;
  873.   }
  874.  
  875.   fclose (InputFile);
  876.   InputFile = NULL;
  877.  
  878.   /* Do a little bit of sanity checking on the volume table of contents. */
  879.  
  880.   Corrupt = FALSE;
  881.   VTOCPntr = DiskImageAtSector (0x11, 0);
  882.  
  883.   if (VTOCPntr->dosVersion != 3)
  884.   {
  885.     printf ("DOS version %d isn't 3.\n", (int) VTOCPntr->dosVersion);
  886.     Corrupt = TRUE;
  887.   }
  888.  
  889.   VolumeNumber = VTOCPntr->volumeNumber;
  890.   if (Verbose)
  891.     printf ("Disk volume #%d.\n", VolumeNumber);
  892.  
  893.   if (VTOCPntr->tracksPerDisk != 35)
  894.   {
  895.     printf ("The VTOC claims %d tracks per disk, it needs to be 35.\n",
  896.      (int) VTOCPntr->tracksPerDisk);
  897.     Corrupt = TRUE;
  898.   }
  899.  
  900.   if (VTOCPntr->sectorsPerTrack != 16)
  901.   {
  902.     printf ("The VTOC claims %d sectors per track, it needs to be 16.\n",
  903.      (int) VTOCPntr->sectorsPerTrack);
  904.     Corrupt = TRUE;
  905.   }
  906.  
  907.   BytesPerSector =
  908.     VTOCPntr->bytesPerSectorLowByte +
  909.     256 * VTOCPntr->bytesPerSectorHighByte;
  910.   if (BytesPerSector != 256)
  911.   {
  912.     printf ("The VTOC claims %d bytes per sector, it needs to be 256.\n",
  913.      (int) BytesPerSector);
  914.     Corrupt = TRUE;
  915.   }
  916.  
  917.   if (Corrupt)
  918.   {
  919.     printf ("This doesn't appear to be an Apple DOS 3.3 format disk.\n");
  920.     return FALSE;
  921.   }
  922.  
  923.   DirectoryTS.track = VTOCPntr->firstDirectory.track;
  924.   DirectoryTS.sector = VTOCPntr->firstDirectory.sector;
  925.  
  926.   while (TRUE)
  927.   {
  928.     if (Verbose)
  929.       printf ("Now doing directory sector $%02X/%X:\n",
  930.       (int) DirectoryTS.track, (int) DirectoryTS.sector);
  931.  
  932.     DirectoryPntr = DiskImageAtSector (DirectoryTS.track, DirectoryTS.sector);
  933.     if (DirectoryPntr == NULL)
  934.       break; /* Reached end of sector list (0,0) or bad track/sector. */
  935.  
  936.     for (FileNumber = 0; FileNumber < 7; FileNumber++)
  937.     {
  938.       FileEntryPntr = DirectoryPntr->fileEntry + FileNumber;
  939.  
  940.       if (!DumpSingleFile (FileEntryPntr))
  941.         return FALSE;
  942.     }
  943.  
  944.     /* Get next directory sector, watch out for
  945.        odd alignment of the TSPair field. */
  946.  
  947.     DirectoryTS.track = DirectoryPntr->nextDirectory.track;
  948.     DirectoryTS.sector = DirectoryPntr->nextDirectory.sector;
  949.   }
  950.  
  951.   return TRUE;
  952. }
  953.  
  954.  
  955.  
  956. /******************************************************************************
  957.  * Called at program exit to clean up globally allocated things.
  958.  */
  959.  
  960. void CleanUp (void)
  961. {
  962.   if (InputFile != NULL)
  963.   {
  964.     fclose (InputFile);
  965.     InputFile = NULL;
  966.   }
  967.  
  968.   if (OutputFile != NULL)
  969.   {
  970.     fclose (OutputFile);
  971.     OutputFile = NULL;
  972.   }
  973. }
  974.  
  975.  
  976.  
  977. /******************************************************************************
  978.  * The usual main program entry point.
  979.  */
  980.  
  981. int main (void)
  982. {
  983.   atexit (CleanUp);
  984.  
  985.   if (!ParseArguments ())
  986.     return 10; /* Failure somewhere, or run from Workbench, not shell. */
  987.  
  988.   if (!BreakUpDiskImage ())
  989.     return 20; /* Something went wrong. */
  990.  
  991.   if (Verbose)
  992.     printf ("Done!\n");
  993.  
  994.   return 0;  /* Success! */
  995. }
  996.